package com.chiu.widget;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;

/**
 * Created by zhouqing.qiu on 15-10-6.
 */
public class PullToZoomListView extends ListView {

    private static final int ANIMATION_DURATION = 500;
    private static final float SCROLL_FACTOR = 0.5f;
    private static final int MAX_OVERSTEP_LENGTH = 500;
    private static final int SCROLL_DIRECTION_UP = 1;
    private static final int SCROLL_DIRECTION_DOWN = 2;

    private float mFirstDownY;
    private float mDownY;
    private float mMaxScaleY;
    private float mScaleY;

    public PullToZoomListView(Context context) {
        super(context);
    }

    public PullToZoomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PullToZoomListView);
        mMaxScaleY = array.getFloat(R.styleable.PullToZoomListView_max_scale_ratio, 1.2f);
        setVerticalFadingEdgeEnabled(false);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mFirstDownY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                mDownY = ev.getY();
                float deltaY = mDownY - mFirstDownY;
                if (mScaleY < mMaxScaleY) {
                    if (isScrollToTop() && deltaY > 0) {
                        mScaleY = 1.0f + deltaY / MAX_OVERSTEP_LENGTH * (mMaxScaleY - 1.0f) * SCROLL_FACTOR;
                        setPivotY(0);
                        setScaleY(mScaleY);
                    } else if (isScrollToBottom() && deltaY < 0) {
                        deltaY = Math.abs(deltaY);
                        mScaleY = 1.0f + deltaY / MAX_OVERSTEP_LENGTH * (mMaxScaleY - 1.0f) * SCROLL_FACTOR;
                        setPivotY(getHeight());
                        setScaleY(mScaleY);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mScaleY > 1) {
                    restoreScaleAnim(mScaleY);
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    private boolean isScrollToTop() {
        int position = getFirstVisiblePosition();
        if (position == 0) {
            View firstItem = getChildAt(position);
            if (firstItem != null && firstItem.getTop() >= 0) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    private boolean isScrollToBottom() {
        int position = getLastVisiblePosition();
        if (position == getCount() - 1) {
            View lastItem = getChildAt(getChildCount() - 1);
            if (lastItem != null && lastItem.getBottom() <= getHeight()) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    private void restoreScaleAnim(float scale) {
        ObjectAnimator scaleRestoreAnim = ObjectAnimator.ofFloat(this, "chiu", scale, 1.0f);
        scaleRestoreAnim.setDuration(ANIMATION_DURATION);
        scaleRestoreAnim.setInterpolator(new ScaleInterpolator());
        scaleRestoreAnim.start();
        scaleRestoreAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mScaleY = value;
                setScaleY(value);
            }
        });
    }

    public void beginScaleAnim(final int direction) {
        ObjectAnimator zoomAnim = ObjectAnimator.ofFloat(this, "chiu", 1.0f, mMaxScaleY);
        zoomAnim.setDuration(ANIMATION_DURATION);
        zoomAnim.setInterpolator(new ScaleInterpolator());
        zoomAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                if (direction == SCROLL_DIRECTION_UP) {
                    setPivotY(0);
                    setScaleY(value);
                } else if (direction == SCROLL_DIRECTION_DOWN) {
                    setPivotY(getHeight());
                    setScaleY(value);
                }
            }
        });
        ObjectAnimator narrowAnim = ObjectAnimator.ofFloat(this, "chiu", mMaxScaleY, 1.0f);
        narrowAnim.setDuration(ANIMATION_DURATION);
        narrowAnim.setInterpolator(new ScaleInterpolator());
        narrowAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                setScaleY(value);
            }
        });

        AnimatorSet animSet = new AnimatorSet();
        animSet.play(narrowAnim).after(zoomAnim);
        animSet.start();
    }

    class ScaleInterpolator implements TimeInterpolator {
        private static final float DEFAULT_TENSION = 1.5f;
        private float mTension;

        public ScaleInterpolator() {
            mTension = DEFAULT_TENSION;
        }

        public void setDistance(int distance) {
            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
        }

        @Override
        public float getInterpolation(float t) {
            t -= 1.0f;
            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
        }
    }
}
